//! Runtime helpers for [turbo-tasks-macro].

pub use async_trait::async_trait;
pub use bincode;
pub use once_cell::sync::{Lazy, OnceCell};
use rustc_hash::FxHashMap;
pub use serde;
pub use shrink_to_fit;
pub use tracing;

use crate::{
    FxDashMap, NonLocalValue, RawVc, TaskInput, TaskPersistence, TraitTypeId, ValueType,
    ValueTypeId, Vc, debug::ValueDebugFormatString, task::TaskOutput,
};
pub use crate::{
    global_name, inventory_submit,
    magic_any::MagicAny,
    manager::{find_cell_by_id, find_cell_by_type, spawn_detached_for_testing},
    native_function::{
        CollectableFunction, NativeFunction, downcast_args_owned, downcast_args_ref,
    },
    value_type::{CollectableTrait, CollectableValueType},
};

#[inline(never)]
pub async fn value_debug_format_field(value: ValueDebugFormatString<'_>) -> String {
    match value.try_to_value_debug_string().await {
        Ok(result) => match result.await {
            Ok(result) => result.to_string(),
            Err(err) => format!("{err:?}"),
        },
        Err(err) => format!("{err:?}"),
    }
}

pub fn get_persistence_from_inputs(inputs: &impl TaskInput) -> TaskPersistence {
    if inputs.is_transient() {
        TaskPersistence::Transient
    } else {
        TaskPersistence::Persistent
    }
}

pub fn get_persistence_from_inputs_and_this(
    this: RawVc,
    inputs: &impl TaskInput,
) -> TaskPersistence {
    if this.is_transient() || inputs.is_transient() {
        TaskPersistence::Transient
    } else {
        TaskPersistence::Persistent
    }
}

pub fn assert_returns_non_local_value<ReturnType, Rv>()
where
    ReturnType: TaskOutput<Return = Vc<Rv>>,
    Rv: NonLocalValue + Send,
{
}

pub fn assert_argument_is_non_local_value<Argument: NonLocalValue>() {}

#[macro_export]
macro_rules! stringify_path {
    ($path:path) => {
        stringify!($path)
    };
}

/// Rexport std::ptr::metadata so not every crate needs to enable the feature when they use our
/// macros.
#[inline(always)]
pub const fn metadata<T: ?Sized>(ptr: *const T) -> <T as std::ptr::Pointee>::Metadata {
    // Ideally we would just `pub use std::ptr::metadata;` but this doesn't seem to work.
    std::ptr::metadata(ptr)
}

/// A registry of all the impl vtables for a given VcValue trait
/// This is constructed in the macro gencode and populated by the registry.
#[derive(Default)]
pub struct VTableRegistry<T: ?Sized> {
    map: FxHashMap<ValueTypeId, <T as std::ptr::Pointee>::Metadata>,
}

impl<T: ?Sized> VTableRegistry<T> {
    pub fn new(id: TraitTypeId) -> Self {
        let mut map = FxHashMap::default();
        match TRAIT_CAST_FNS.remove(&id) {
            Some((_, impls)) => {
                for (value_type_id, RawPtr(raw_fn)) in impls {
                    // SAFETY: These are generated by the macro gencode in value_impl with this
                    // signature.
                    let cast_fn: fn(*const ()) -> *const T = unsafe { std::mem::transmute(raw_fn) };
                    // Cast a null pointer to a fat pointer using the cast_fn, this allows us to
                    // capture a vtable Alternatively we could just store the
                    // cast functions but it will be faster to call 'from_raw_parts' instead of an
                    // indirect function call.
                    let ptr = cast_fn(std::ptr::null::<()>());
                    let metadata = std::ptr::metadata(ptr);
                    let prev = map.insert(value_type_id, metadata);
                    debug_assert!(
                        prev.is_none(),
                        "multiple cast functions registered for {value_type_id}"
                    )
                }
            }
            None => {
                // A trait doesn't have to have any implementations.
            }
        }

        Self { map }
    }

    pub(crate) fn cast(&self, id: ValueTypeId, raw: *const ()) -> *const T {
        let metadata = self.map.get(&id).unwrap();
        std::ptr::from_raw_parts(raw, *metadata)
    }
}

struct RawPtr(*const ());
// SAFETY: We only store function pointers in here which are safe to send/sync
unsafe impl Sync for RawPtr {}
unsafe impl Send for RawPtr {}

// Accumulate all trait impls by trait id
static TRAIT_CAST_FNS: Lazy<FxDashMap<TraitTypeId, Vec<(ValueTypeId, RawPtr)>>> = Lazy::new(|| {
    let map: FxDashMap<TraitTypeId, Vec<(ValueTypeId, RawPtr)>> = FxDashMap::default();
    for CollectableTraitCastFunctions(trait_id_fn, value_id_fn, cast_fn) in
        inventory::iter::<CollectableTraitCastFunctions>
    {
        map.entry(trait_id_fn())
            .or_default()
            .value_mut()
            .push((value_id_fn(), RawPtr(*cast_fn)));
    }
    map
});

// Holds a raw pointer to a function that can perform a fat pointer cast
pub struct CollectableTraitCastFunctions(
    pub fn() -> TraitTypeId,
    pub fn() -> ValueTypeId,
    pub *const (),
);
// SAFETY: We only store function pointers in here.
unsafe impl Sync for CollectableTraitCastFunctions {}
inventory::collect! {CollectableTraitCastFunctions}

#[allow(clippy::type_complexity)]
pub struct CollectableTraitMethods(
    // A value type name
    pub &'static str,
    pub fn() -> (TraitTypeId, Vec<(&'static str, &'static NativeFunction)>),
);
inventory::collect! {CollectableTraitMethods}

// Called when initializing ValueTypes by value_impl
pub fn register_trait_methods(value: &mut ValueType) {
    #[allow(clippy::type_complexity)]
    static TRAIT_METHODS_BY_VALUE: Lazy<
        FxDashMap<&'static str, Vec<(TraitTypeId, Vec<(&'static str, &'static NativeFunction)>)>>,
    > = Lazy::new(|| {
        let map: FxDashMap<&'static str, Vec<_>> = FxDashMap::default();
        for CollectableTraitMethods(value_name, thunk) in inventory::iter::<CollectableTraitMethods>
        {
            map.entry(*value_name).or_default().push(thunk());
        }
        map
    });
    match TRAIT_METHODS_BY_VALUE.remove(value.global_name) {
        Some((_, traits)) => {
            for (trait_type_id, methods) in traits {
                let trait_type = crate::registry::get_trait(trait_type_id);
                value.register_trait(trait_type_id);
                for (name, method) in methods {
                    value.register_trait_method(trait_type.get(name), method);
                }
            }
        }
        None => {
            // do nothing, values don't have to implement any traits
        }
    }
}

/// Submit an item to the inventory.
///
/// This macro is a wrapper around `inventory::submit` that adds a `#[not(cfg(rust_analyzer))]`
/// attribute to the item. This is to avoid warnings about unused items when using Rust Analyzer.
#[macro_export]
macro_rules! inventory_submit {
    ($($item:tt)*) => {
        #[cfg(not(rust_analyzer))]
        $crate::macro_helpers::inventory_submit_inner! { $($item)* }
    }
}

/// Exported so the above macro can reference it.
#[doc(hidden)]
pub use inventory::submit as inventory_submit_inner;

/// Define a global name for a turbo-tasks value.
#[cfg(not(rust_analyzer))] // ignore-rust-analyzer due to https://github.com/rust-lang/rust-analyzer/issues/19993
#[macro_export]
macro_rules! global_name {
    ($($item:tt)*) => {

        ::std::concat!(::std::env!("CARGO_PKG_NAME"), "@", ::std::module_path!(), "::", $($item)*)
    }
}
/// Define a global name for a turbo-tasks value.
/// This has a dummy implementation for Rust Analyzer to avoid https://github.com/rust-lang/rust-analyzer/issues/19993
#[cfg(rust_analyzer)]
#[macro_export]
macro_rules! global_name {
    ($($item:tt)*) => {
        $($item)*
    }
}
